import { BaseComponent } from "../type";
import { FormItem } from "./form-item";
import {
	BasicFormOptions,
	ButtonItemOptions,
	FormBox,
	FormItemOptions,
	FormItemRuleKey,
} from "./type";
import { VNode, RendererNode, RendererElement, createVNode } from "vue";
import FormBoxVue from "./form-box.vue";
import { FormInstance, FormItemRule } from "element-plus";
import { ButtonItem } from "./button-item";
import { FormItemComponentType } from "./components";
import { message } from "../popup";

const recordCallbackBeforeInit: Map<"clear" | "reset", boolean> = new Map();

export class BasicFormBox<T = Record<string, any>> implements FormBox<T>, BaseComponent {
	private formItem: Map<keyof T, FormItem<T>> = new Map();
	private buttonItem: Map<string, ButtonItem> = new Map();
	private type: BasicFormOptions<T>["type"] = "edit";
	private lineNum: BasicFormOptions<T>["lineNum"] = 1;
	private layout: BasicFormOptions<T>["layout"] = "horizontal";
	private labelWidth: BasicFormOptions<T>["labelWidth"] = "";
	private fromRef: FormInstance | undefined;
	private width: BasicFormOptions<T>["width"] = "";
	//校验规则Map
	private rulesMap: Map<FormItemRuleKey, Array<FormItemRule>> = new Map();

	constructor(opts: BasicFormOptions<T>) {
		this.type = opts.type;
		this.lineNum = opts.lineNum;
		this.layout = opts.layout!;
		this.labelWidth = opts.labelWidth!;
		this.width = opts.width!;
		opts.options.forEach(item => {
			this.addFormItem(item);
		});
		this.configDefaultRule();
	}
	/**
	 * 设置表单type
	 * @param type
	 */
	setType(type: BasicFormOptions<T>["type"]) {
		this.type = type;
	}
	setLineNum(number: number) {
		this.lineNum = number;
	}
	setLayout(layout: "horizontal" | "vertical") {
		this.layout = layout;
	}
	/**
	 * 设置表单labelWidth
	 * @param labelWidth
	 */
	setLabelWidth(labelWidth: BasicFormOptions<T>["labelWidth"]) {
		this.labelWidth = labelWidth;
	}
	/**默认校验规则 */
	private configDefaultRule() {
		this.rulesMap.set("Number", [
			{ type: "number", required: true, message: "请输入数字", trigger: "blur" },
		]);
		this.rulesMap.set("PhoneNumber", [
			{ min: 11, required: true, message: "手机号不足11位", trigger: "blur" },
			{
				validator: (_rule, value, callback) => {
					if (!value) {
						return callback(new Error("手机号不能为空"));
					} else {
						const reg = /^1[3-9][0-9]\d{8}$/;
						if (reg.test(value)) {
							callback();
						} else {
							return callback(new Error("请输入正确的手机号"));
						}
					}
				},
				trigger: "blur",
			},
		]);
	}

	/**新增表单项*/
	addFormItem(opts: FormItemOptions<T>): FormItem<T> {
		const item = new FormItem(opts);
		this.formItem.set(opts.key, item);
		if (item.getOptions().rule === "RequiredRule") {
			if (item.getOptions().placeholder) {
				item.setRule([
					{
						required: true,
						message: item.getOptions().placeholder,
						trigger: "blur",
					},
				]);
			}
		} else {
			const ruleKey = item.getOptions().rule;
			if (typeof ruleKey == "string") {
				const arr = this.rulesMap.get(ruleKey);
				if (arr) {
					item.setRule(arr);
				}
			}
		}
		return this.formItem.get(opts.key)!;
	}

	/** 删除表单项*/
	removeFormItem(key: keyof T): void {
		const result = this.formItem.delete(key as keyof T);
		if (!result) {
			throw new Error("删除表单项失败");
		}
	}

	/** 根据key获取表单项数据*/
	getFormItemValue(key: keyof T): FormItem<T> {
		return this.formItem.get(key)!;
	}

	/** 根据key设置表单项数据*/
	setFormItemValue(key: keyof T, value: T[keyof T]): void {
		const result = this.getFormItemValue(key);
		if (result) {
			result.setValue(value);
		}
	}

	//获取表单数据，自动进行表单校验
	getFormValue(): T {
		const fromObj = {} as T;
		if (!this.formItem.size) {
			return {} as T;
		}
		for (const [key, value] of this.formItem) {
			fromObj[key] = value.getValue();
		}
		return fromObj;
	}

	//设置表单数据
	setFormValue(form: T): void {
		for (const key in form) {
			const value = form[key];
			this.setFormItemValue(key, value);
		}
	}

	//设置表单某项禁用
	setFormItemDisabled(key: string, state: boolean): void {
		const item = this.getFormItemValue(key as keyof T);
		if (item) {
			item.setDisabled(state);
		} else {
			throw new Error(`请传入正确的key,设置表单按钮禁用,key:${key}`);
		}
	}

	// 设置表单禁用
	setFormDisabled(state: boolean): void {
		if (!this.formItem.size) {
			throw new Error("表单为空，无法设置");
		}
		for (let value of this.formItem.values()) {
			value.setDisabled(state);
		}
	}

	//获取当前表单的配置项
	getFormConf(): FormItemOptions<T>[] {
		const arr: FormItemOptions<T>[] = [];
		this.formItem.forEach(e => {
			arr.push(e.getOptions());
		});
		return arr;
	}

	// 动态设置表单配置
	setFormItemConf(key: keyof T, opt: FormItemComponentType) {
		if (this.formItem.has(key)) {
			const target = this.formItem.get(key)!;
			const config = target.getOptions();
			config.conf = { ...config.conf, ...opt.conf } as any;
			target.setOptions(config);
		}
	}
	/**
	 * 清空表单数据
	 * @returns
	 */
	clear() {
		// 判断当前是否为清除状态
		if (!this.fromRef && !recordCallbackBeforeInit.has("clear")) {
			recordCallbackBeforeInit.set("clear", true);
			return;
		}
		// 清空表单校验
		this.fromRef?.resetFields();
		const formData = this.fromRef?.$props.model as T;
		// 循环清空表单数据
		for (const key in formData) {
			if (Object.prototype.hasOwnProperty.call(formData, key)) {
				const formItem = this.formItem.get(key);
				if (formItem) {
					const val = formData[key];
					if (typeof val === "string") formItem.setValue("" as any);
					else if (typeof val === "number") formItem.setValue(0 as any);
					else if (val instanceof Array) formItem.setValue([] as any);
					else if (typeof val === "object" && val !== null) formItem.setValue({} as any);
					else formItem.setValue(null as any);
				}
			}
		}
	}

	// 重置表单
	reset(): void {
		// 判断当前是否为重置状态
		if (!this.fromRef && !recordCallbackBeforeInit.has("reset")) {
			recordCallbackBeforeInit.set("reset", true);
			return;
		}
		// 清空表单校验
		this.fromRef?.resetFields();
		const formData = this.fromRef?.$props.model as T;
		// 循环重置表单数据
		for (const key in formData) {
			if (Object.prototype.hasOwnProperty.call(formData, key)) {
				const formItem = this.formItem.get(key);
				if (formItem) {
					formItem.setValue(formData[key]);
				}
			}
		}
	}

	//表单校验
	async verify(): Promise<boolean> {
		return new Promise(resolve => {
			this.fromRef?.validate((valid, fields) => {
				if (valid) {
					resolve(true);
				} else {
					resolve(false);
					message.open({
						type: "warning",
						message: "请将表单项填写完整!",
						title: "提示",
					});
					console.log("error submit!", fields);
				}
			});
		});
	}
	/**
	 * 获取表单配置项
	 * @returns
	 */
	getOption() {
		return {
			options: this.formatFormItemOptions(),
			lineNum: this.lineNum,
			type: this.type || "edit",
			layout: this.layout || "horizontal",
			labelWidth: this.labelWidth,
			width: this.width,
			btnList: this.getFormatBtnItemOptions(),
			change: (k: keyof T, v: T[keyof T]) => {
				this.setFormItemValue(k, v);
			},
			init: (ref: FormInstance) => {
				this.fromRef = ref;
				recordCallbackBeforeInit.forEach((item, key) => {
					item && this[key]();
				});
				recordCallbackBeforeInit.clear();
			},
		};
	}
	/**
	 * 获取表单vnode
	 * @returns
	 */
	getVNode(): VNode<RendererNode, RendererElement, { [key: string]: any }> {
		return createVNode(FormBoxVue, {
			options: this.getOption(),
		});
	}
	/**
	 * 将formItem对象转换为表单的配置
	 * @returns
	 */
	private formatFormItemOptions() {
		const arr: FormItemOptions<T>[] = [];
		this.formItem.forEach(e => {
			arr.push(e.getOptions());
		});
		return arr;
	}

	/**新增表单按钮 */
	addFormButton(opts: ButtonItemOptions): ButtonItem {
		const btnItem = new ButtonItem(opts);
		this.buttonItem.set(opts.key, btnItem);
		return this.buttonItem.get(opts.key)!;
	}

	/**移除表单按钮 */
	removeFormButton(key: string): void {
		const result = this.buttonItem.delete(key);
		if (!result) {
			throw new Error("移出按钮失败");
		}
	}

	/**设置表单按钮禁用 */
	setFormButtonDisabled(key: string, state: boolean): void {
		const btnItem = this.buttonItem.get(key);
		if (btnItem) {
			btnItem.setButtonDisable(state);
		} else {
			throw new Error(`请传入正确的key,设置表单按钮禁用,key:${key}`);
		}
	}

	/**将btnItem 转换为表单配置 */
	getFormatBtnItemOptions() {
		const arr: ButtonItemOptions[] = [];
		this.buttonItem.forEach(e => {
			arr.push(e.getOptions());
		});
		return arr;
	}
}
